package ipcards.net;
import ipcards.*;
import ipcards.events.*;

import java.awt.Point;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;


/**
 * Represents a connection to a remote server
 * 
 * @author Ricky Vincent
 */
public class Connection implements Runnable, PlayerActionPerformer {

	private static final String END_LINE = "\n";
	
	private Socket socket;
	private DataOutputStream out;
	private BufferedReader in;
	
	private boolean connected = false;
	
	private Room room;
	private Player player;
	
	/**
	 * Creates a connection to a remote server. Sets up the output and input streams.
	 * 
	 * @param host The hostname of the server to connect to.
	 * @param port The port of the server to connect to.
	 * @throws IOException 
	 * @throws UnknownHostException 
	 */
	public Connection (String host, int port) throws UnknownHostException, IOException {
		socket = new Socket(host, port);
		out = new DataOutputStream(socket.getOutputStream());
		in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		connected = true;
	}
	
	/**
	 * Receives data from the socket (and decrypts it?)
	 */
	public void receiveData() {
		
		String input = null;
		
		while(connected) {
			try {
				input = in.readLine();
				commandHandler(input);
				
			} catch (IOException e) {
				
				System.err.println("Lost connection to server... retrying");
				
				try {
					//sleep for 1 second between reconnection attempts
					Thread.sleep(1000);
				} catch (InterruptedException e1) {
					continue;
				}
			}
		}
	}
	
	
	/**
	 * Handles commands. Performs PlayerActions on the PlayerModel.
	 * 
	 * @param command the command to handle.
	 */
	private void commandHandler (String command) {
		
		
		Dbg.clientLog(command);
		String args = command.substring(1);
		
		switch (command.charAt(0)) {
			
		case PlayerAction.SYNC_LOCATIONS:
			player.performPlayerAction(new PlayerAction(this, PlayerAction.SYNC_LOCATIONS, args));
			break;
			
		case PlayerAction.FLIP:
			player.performPlayerAction(PlayerAction.newFlip(this, room.table.getCardSetFromIDs(args)));
			break;
			
		case PlayerAction.LOCAL_FLIP:
			player.performPlayerAction(PlayerAction.newLocalFlip(this, room.table.getCardSetFromIDs(args)));
			break;
		
		case PlayerAction.MOVE:
			String[] cargs = args.split(PlayerAction.DEL + "");
			String cardsString = command.substring(1 + cargs[0].length() + 1 * 2 + cargs[1].length());
			player.performPlayerAction(PlayerAction.newMove(this, room.table.getCardSetFromIDs(cardsString), new Point(Integer.valueOf(cargs[0]), Integer.valueOf(cargs[1]))));
			break;
			
		case PlayerAction.ADD_DECK:
			player.performPlayerAction(new PlayerAction(this, PlayerAction.ADD_DECK, args));
			break;
			
		case PlayerAction.NEW_GAME:
			player.performPlayerAction(new PlayerAction(this, PlayerAction.NEW_GAME, args));
			performPlayerAction(new PlayerAction(this, PlayerAction.SYNC_LOCATIONS));
			break;
			
		case PlayerAction.SET_FACE_UP:
			player.performPlayerAction(new PlayerAction(this, room.table.getCardSetFromIDs(args), PlayerAction.SET_FACE_UP));
			break;
			
		case PlayerAction.SET_FACE_DOWN:
			player.performPlayerAction(new PlayerAction(this, room.table.getCardSetFromIDs(args), PlayerAction.SET_FACE_DOWN));
			break;
			
		case PlayerAction.RESET_LOCAL_FLIP:
			player.performPlayerAction(new PlayerAction(this, room.table.getCardSetFromIDs(args), PlayerAction.RESET_LOCAL_FLIP));
			break;	
		
		//CUSTOM actions go to the game
		case PlayerAction.CUSTOM:
			room.table.game.playerActionPerformed(new PlayerAction(this, PlayerAction.CUSTOM, args));
			break;			
		
		case PlayerAction.SHOW_DIALOG:
			room.showDialog(args.replace("\t", " "));
			break;
		
		case PlayerAction.SEND_NAME_HASHMAP:
			player.performPlayerAction(new PlayerAction(this, PlayerAction.SEND_NAME_HASHMAP, args));
			break;		
			
		case PlayerAction.END_TURN:
			room.table.turn = Integer.parseInt(args.trim());
			break;
						
		}
		
	}

	
	public void run() {
		receiveData();		
	}
	
	
	public void updateDebug() {
		//Controller.debug.update(room.table.cards);
	}
	
	
	/**
	 * Disconnects from the server.
	 */
	public void disconnect() {
		connected = false;
	}

	public void setRoom(Room r) {
		room = r;
		player = room.localPlayer;
	}
	

	/**
	 * Writes bytes directly to the <code>Socket</code>.
	 * 
	 * @param data the array of bytes to write.
	 * @throws IOException
	 */
	private void sendData (byte[] data) throws IOException {
		out.write(data);
		out.flush();
	}
	
	
	/**
	 * Sends a command to this <code>Client</code>
	 * 
	 * @param command The command to send.
	 * @param args The arguments to send with the command.
	 * @throws IOException
	 */
	public void sendCommand(char command, String args) {
		updateDebug();
		args = command + args + END_LINE;
		try {
			sendData(args.getBytes());
		} catch (IOException e) {
			
		}
	}
	
	
	
	/**
	 * Called when a PlayerAction is fired. 
	 * Receives PlayerActions from the GUI and the server Connection.
	 */
	public void performPlayerAction(PlayerAction pa) {

		switch (pa.getAction()) {
		
		case PlayerAction.SELECTION_ADD:
			break;
			
		case PlayerAction.SELECTION_REMOVE:
			break;
			
		case PlayerAction.HOLD:
			sendCommand(PlayerAction.HOLD, player.selectedCards.toString());
			break;
			
		case PlayerAction.RELEASE:
			sendCommand(PlayerAction.SYNC_LOCATIONS, null);
			break;
			
		case PlayerAction.MOVE:
			sendCommand(PlayerAction.MOVE, pa.getChange().x + " " + pa.getChange().y);
			break;
			
		case PlayerAction.FLIP:
			sendCommand(PlayerAction.FLIP, null);
			break;
			
		case PlayerAction.LOCAL_FLIP:
			sendCommand(PlayerAction.LOCAL_FLIP, null);
			break;	
			
		case PlayerAction.SHUFFLE:
			sendCommand(PlayerAction.SHUFFLE, null);
			break;
			
		case PlayerAction.NEW_GAME:		
			sendCommand(PlayerAction.NEW_GAME, pa.getArgs());
			break;
		
			
		case PlayerAction.ADD_DECK:
			sendCommand(PlayerAction.ADD_DECK, pa.getArgs());
			break;
		
			
		case PlayerAction.SYNC_LOCATIONS:
			sendCommand(PlayerAction.SYNC_LOCATIONS, null);
			break;
		
		case PlayerAction.SET_FACE_UP:
			sendCommand(PlayerAction.SET_FACE_UP, pa.getArgs());
			break;
			
		case PlayerAction.SET_FACE_DOWN:
			sendCommand(PlayerAction.SET_FACE_DOWN, pa.getArgs());
			break;
			
		case PlayerAction.CUSTOM:
			sendCommand(PlayerAction.CUSTOM, pa.getArgs());
			break;
		}
	}
	
}
